package com.beargod.aubridge

import android.util.Base64
import android.util.Log
import com.google.protobuf.Message
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

/**
 * 实现将和Unity通信的核心功能。负责将Unity的请求派发给对应的处理器和向Unity发送请求并等待回复
 */
public abstract class UnityAgent: IDispatcher {
    private val waiters: MutableMap<Int, Array<Continuation<String?>?>> = HashMap(0)
    private val handlers: MutableMap<Int, HandlerBase<*, *>> = HashMap()

    /**
     * 给Unity发送消息的具体实现
     */
    protected abstract fun sendUnityMessage(gameObject: String, methodName: String, message: String)

    /**
     * 调用Unity的接口
     *
     * @param interfaceCode 要调用接口ID
     * @param p 调用参数
     * @return 调用结果，如何消息无法发送，返回null
     */
    suspend fun <TParam : Message> ask(interfaceCode: Int, p: TParam): ByteArray? {
        val b64 = suspendCoroutine { c ->
            val order = enqueue(interfaceCode, c)
            if (order == null) {
                c.resume(null)
            } else {
                askUnity(interfaceCode, order, p)
            }
        }

        return Base64.decode(b64, Base64.DEFAULT)
    }

    /**
     * 通知从Unity等到了响应
     *
     * @param reply 响应
     */
    fun notify(reply: Reply) {
        val e = reply.interfaceCode;
        val order = reply.order
        if (order < 0) {
            Log.e(TAG, "Got an invalid order: $order, interface: ${reply.interfaceCode}")
            return
        }

        if (waiters.containsKey(e)) {
            val arr = waiters[e]!!;
            if (order >= arr.size) {
                Log.e(TAG, "Got an too big order: $order, interface: ${reply.interfaceCode}")
                return
            }

            val c = arr[order]
            if (c == null) {
                Log.e(TAG, "Not waiter for interface: ${reply.interfaceCode}: order $order")
                return
            } else {
                arr[order] = null
                if (reply.apiResultCode == ApiResultCode.Ok) {
                    c.resume(reply.message)
                } else {
                    Log.e(
                        TAG,
                        "Got an error for interface: ${reply.interfaceCode}: code: ${reply.apiResultCode}, order $order"
                    )
                    c.resume(null)
                }
            }
        } else {
            Log.e(TAG, "Native never asked ${reply.interfaceCode}")
            return
        }
    }

    private fun enqueue(interfaceCode: Int, continuation: Continuation<String?>): Int? {
        val e = interfaceCode;
        val arr: Array<Continuation<String?>?>;
        if (waiters.containsKey(e)) {
            arr = waiters[e]!!
        } else {
            arr = arrayOfNulls(MAX_ORDER_COUNT);
            waiters[e] = arr;
        }

        for (i in arr.indices) {
            if (arr[i] == null) {
                arr[i] = continuation
                return i;
            }
        }

        Log.e(TAG, "Failed to wait ${interfaceCode}, too many waiters")
        return null;
    }

    private fun askUnity(interfaceCode: Int, order: Int, message: Message) {
        val b64 = Base64.encodeToString(message.toByteArray(), Base64.DEFAULT)
        val ask = Ask(interfaceCode, order, b64)
        sendUnityMessage("NativeListener", "NativeAsk", Json.encodeToString(ask))
    }

    override fun register(handlerBase: HandlerBase<*, *>) {
        handlers[handlerBase.interfaceCode] = handlerBase
    }

    override fun dispatch(ask: Ask) {
        val order = ask.order

        val h = handlers[ask.interfaceCode]
        if (h == null) {
            replyApiNotFoundToUnity(ask.interfaceCode, order)
            return
        }

        val interfaceCode = ask.interfaceCode
        if (h is ILockable) {
            if (h.isLocked) {
                replyErrorToUnity(interfaceCode, order, ApiResultCode.Repeated)
                return
            }
        }

        CoroutineScope(Dispatchers.IO).launch {
            val result = h.handle(order, ask.message)
            if (result.apiResultCode == ApiResultCode.Ok) {
                replyUnity(interfaceCode, order, result.result!!)
            } else {
                replyErrorToUnity(interfaceCode, order, result.apiResultCode)
            }
        }
    }

    private fun replyUnity(interfaceCode: Int, order: Int, message: Message) {
        val b64 = Base64.encodeToString(message.toByteArray(), Base64.DEFAULT)
        val reply = Reply(ApiResultCode.Ok, interfaceCode, order, b64);

        sendUnityMessage("NativeListener", "NativeReply", Json.encodeToString(reply))
    }

    private fun replyApiNotFoundToUnity(interfaceCode: Int, order: Int) {
        val reply = Reply(ApiResultCode.NotFound, interfaceCode, order, "")
        sendUnityMessage("NativeListener", "NativeReply", Json.encodeToString(reply))
    }

    private fun replyErrorToUnity(interfaceCode: Int, order: Int, code: ApiResultCode) {
        if (code == ApiResultCode.Ok) {
            Log.e(TAG, "Can not return Ok from this interface")
            return
        }

        val reply = Reply(code, interfaceCode, order, "")

        sendUnityMessage("NativeListener", "NativeReply", Json.encodeToString(reply))
    }

    companion object {
        private const val TAG = "UnityAgent"
        private const val MAX_ORDER_COUNT: Int = 5;
    }
}
